#include "storage/buffer_pool.h"
#include <iostream>
#include <sstream>

// Initialize buffer pool
BufferPool::BufferPool(size_t capacity, std::unique_ptr<DiskManager> diskManager)
    : capacity(capacity), hitCount(0), missCount(0), diskManager(std::move(diskManager)) {
    logFunc("Buffer pool initialized, capacity: " + std::to_string(capacity));
}

// Update LRU cache strategy when getting pages
Page* BufferPool::getPage(uint32_t pageId) {
    auto it = pages.find(pageId);
    if (it != pages.end()) {
        // Cache hit, update LRU queue
        lruList.remove(pageId);
        lruList.push_front(pageId);
        hitCount++;
        
        std::stringstream ss;
        ss << "Cache hit page: " << pageId;
        logFunc(ss.str());
        return it->second.get();
    }

    // Page not in buffer, read from disk
    missCount++;
    
    std::stringstream ss;
    ss << "Cache MISS: Page " << pageId;
    logFunc(ss.str());

    // If buffer is full, evict a page
    if (pages.size() >= capacity) {
        evict();
    }

    // Read page from disk
    auto page = std::make_unique<Page>(pageId);
    diskManager->readPage(pageId, page->getData().data());

    // Add to buffer
    auto result = page.get();
    pages[pageId] = std::move(page);
    lruList.push_front(pageId);

    return result;
}

Page* BufferPool::newPage() {
    // If buffer is full, evict a page
    if (pages.size() >= capacity) {
        evict();
    }

    // Allocate new page on disk
    uint32_t pageId = diskManager->allocatePage();

    // Create new page in buffer
    auto page = std::make_unique<Page>(pageId);
    auto result = page.get();
    pages[pageId] = std::move(page);
    lruList.push_front(pageId);

    return result;
}

void BufferPool::flushPage(uint32_t pageId) {
    auto it = pages.find(pageId);
    if (it != pages.end() && it->second->isDirty()) {
        diskManager->writePage(pageId, it->second->getData().data());
        it->second->setDirty(false);
    }
}

void BufferPool::flushAll() {
    for (auto& pair : pages) {
        if (pair.second->isDirty()) {
            diskManager->writePage(pair.first, pair.second->getData().data());
            pair.second->setDirty(false);
        }
    }
}

void BufferPool::evict() {
    if (lruList.empty()) return;

    uint32_t pageId = lruList.back();
    lruList.pop_back();

    auto it = pages.find(pageId);
    if (it != pages.end()) {
        if (it->second->isDirty()) {
            diskManager->writePage(pageId, it->second->getData().data());
        }
        pages.erase(it);
        
        std::stringstream ss;
        ss << "Evicted page: " << pageId;
        logFunc(ss.str());
    }
}

size_t BufferPool::getHitCount() const {
    return hitCount;
}

size_t BufferPool::getMissCount() const {
    return missCount;
}

void BufferPool::setLogFunction(std::function<void(const std::string&)> logFunction) {
    if (logFunction) {
        logFunc = logFunction;
        logFunc("BufferPool log function replaced");
    }
}

void BufferPool::printStats() const {
    std::stringstream ss;
    ss << "BufferPool Statistics:" << std::endl;
    ss << "  Capacity: " << capacity << std::endl;
    ss << "  Current pages in buffer: " << pages.size() << std::endl;
    ss << "  Cache hits: " << hitCount << std::endl;
    ss << "  Cache misses: " << missCount << std::endl;
    
    double hitRate = 0.0;
    if (hitCount + missCount > 0) {
        hitRate = static_cast<double>(hitCount) / (hitCount + missCount) * 100.0;
    }
    
    ss << "  Cache hit rate: " << hitRate << "%";
    logFunc(ss.str());
}

// BufferPool destructor implementation
BufferPool::~BufferPool() {
    // Flush all dirty pages to disk during destruction
    flushAll();
    
    logFunc("Buffer pool released");
}